import fun.parser.Tree;
import ic.ast.decl.*;
import ic.ast.decl.PrimitiveType.DataType;
import ic.ast.expr.*;
import ic.ast.stmt.*;

import java.io.IOException;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


class Factory
{
	protected Class<?> t;
	
	private Factory(Class<?> t)
	{
		this.t = t;
	}

	public static <T extends RNode> Factory establish(Class<T> t)
	{
		return new Factory(t);
	}
	
	public RNode create()
	{
		try { return (RNode)t.newInstance(); } catch (InstantiationException | IllegalAccessException e) { }
		
		return new RNode();
	}
}

interface Functor
{
	public void apply(Object x);
}

class RNode
{
	protected static Map<String,Factory> mapping = new HashMap<String, Factory>();
	protected Map<String,Functor> registers = new HashMap<String,Functor>();

	static
	{
		mapping.put("S", Factory.establish(RNodeS.class));
		mapping.put("class_decl", Factory.establish(RNodeClass.class));
		mapping.put("opt_extends", Factory.establish(RNodeExtends.class));
		mapping.put("field", Factory.establish(RNodeField.class));
		mapping.put("method", Factory.establish(RNodeMethod.class));
		mapping.put("type", Factory.establish(RNodeType.class));
		mapping.put("weaktype", Factory.establish(RNodeType.class));
		mapping.put("formal", Factory.establish(RNodeFormal.class));
		mapping.put("stmt", Factory.establish(RNodeStmt.class));
		mapping.put("stmt_if", Factory.establish(RNodeStmtIf.class));
		mapping.put("stmt_ifelse", Factory.establish(RNodeStmtIf.class));
		mapping.put("stmt_location", Factory.establish(RNodeStmtLocation.class));
		mapping.put("stmt_call", Factory.establish(RNodeStmtCall.class));
		mapping.put("stmt_return", Factory.establish(RNodeStmtReturn.class));
		mapping.put("stmt_while", Factory.establish(RNodeStmtWhile.class));
		mapping.put("stmt_break", Factory.establish(RNodeStmtBreak.class));
		mapping.put("stmt_continue", Factory.establish(RNodeStmtContinue.class));
		mapping.put("stmt_block", Factory.establish(RNodeStmtBlock.class));
		mapping.put("stmt_type", Factory.establish(RNodeStmtType.class));
		mapping.put("expr", Factory.establish(RNodeExpr.class));
		mapping.put("static_call", Factory.establish(RNodeExprStaticCall.class));
		mapping.put("virtual_call", Factory.establish(RNodeExprVirtualCall.class));
		mapping.put("literal", Factory.establish(RNodeExprLiteral.class));
		mapping.put("location", Factory.establish(RNodeExprLocation.class));
		mapping.put("this", Factory.establish(RNodeExprThis.class));
		mapping.put("expr12", Factory.establish(RNodeExprBinaryOp.class));
		mapping.put("expr11", Factory.establish(RNodeExprBinaryOp.class));
		mapping.put("expr10", Factory.establish(RNodeExprBinaryOp.class));
		mapping.put("expr09", Factory.establish(RNodeExprBinaryOp.class));
		mapping.put("expr08", Factory.establish(RNodeExprBinaryOp.class));
		mapping.put("expr07", Factory.establish(RNodeExprBinaryOp.class));
		mapping.put("expr06", Factory.establish(RNodeExprUnaryOp.class));
		mapping.put("expr05", Factory.establish(RNodeExprUnaryOp.class));
		mapping.put("expr04", Factory.establish(RNodeExprNewArray.class));
		mapping.put("expr03", Factory.establish(RNodeExprLength.class));
		mapping.put("expr01", Factory.establish(RNodeExprNewInstance.class));
		
		mapping.put("static_method", Factory.establish(RNodeMethod.class));
	}
	
	public RNode()
	{
	}

	public Object recurse(Tree t, Map<String,Functor> push_registers)
	{
		Map<String,Functor> merged_registers = push_registers;

		for(Tree s : t.subtrees)
		{
			if(!mapping.containsKey(s.root.tag))
			{
				mapping.put(s.root.tag, Factory.establish(RNode.class));
			}
			
			Object x = mapping.get(s.root.tag).create().recurse(s, merged_registers);

			if(merged_registers.containsKey(s.root.tag))
			{
				merged_registers.get(s.root.tag).apply(x);
			}
		}

		return t;
	}
	
	protected Map<String,Functor> merge(Map<String,Functor> lhs, Map<String,Functor> rhs)
	{
		Map<String,Functor> merged = new HashMap<String,Functor>();

		for (Map.Entry<String,Functor> e : lhs.entrySet())
			merged.put(e.getKey(), e.getValue());

		for (Map.Entry<String,Functor> e : rhs.entrySet())
			merged.put(e.getKey(), e.getValue());
		
		return merged;
	}
	
	protected String getValue(Object x)
	{
		return ((Token)((Tree)x).root).getValue();
	}
	
	protected int getLine(Object x)
	{
		return ((Token)((Tree)x).root).getLine();
	}
	
	protected int getColumn(Object x)
	{
		return ((Token)((Tree)x).root).getColumn();
	}
	
	class Mutable<T>
	{
		public T t;
	}
}

class RNodeS extends RNode
{
	protected List<DeclClass> classes = new ArrayList<DeclClass>();

	public RNodeS()
	{
		registers.put("class_decl", new Functor() { public void apply(Object x) { classes.add((DeclClass)x); } });
	}

	@Override
	public Object recurse(Tree t, Map<String,Functor> push_registers)
	{
		super.recurse(t, registers);

		return new Program(classes);
	}
}

class RNodeClass extends RNode
{
	protected int line;
	protected String name;
	protected List<DeclField> fields = new ArrayList<DeclField>();
	protected List<DeclMethod> methods = new ArrayList<DeclMethod>();
	protected String superName;

	public RNodeClass()
	{
		registers.put("CLASS_ID", new Functor() { public void apply(Object x) { line = getLine(x); name = getValue(x); } });
		registers.put("opt_extends", new Functor() { public void apply(Object x) { superName = (String)x; } });
		registers.put("field", new Functor() { @SuppressWarnings("unchecked") public void apply(Object x) { fields.addAll((Collection<DeclField>)x); } });
		registers.put("method", new Functor() { public void apply(Object x) { methods.add((DeclMethod)x); } });
		registers.put("static_method", new Functor() { public void apply(Object x) { methods.add((DeclMethod)x); } });
	}

	@Override
	public Object recurse(Tree t, Map<String,Functor> push_registers)
	{
		super.recurse(t, registers);

		return (name == null) ? new DeclClass(line, name, fields, methods) : new DeclClass(line, name, superName, fields, methods);
	}
}

class RNodeExtends extends RNode
{
	protected String name;

	public RNodeExtends()
	{
		registers.put("CLASS_ID", new Functor() { public void apply(Object x) { name = getValue(x); } });
	}

	@Override
	public Object recurse(Tree t, Map<String,Functor> push_registers)
	{
		super.recurse(t, registers);

		return name;
	}
}

class RNodeField extends RNode
{
	protected Type type;
	protected List<String> ids = new ArrayList<String>();

	public RNodeField()
	{
		registers.put("type", new Functor() { public void apply(Object x) { type = (Type)x; } });
		registers.put("ID", new Functor() { public void apply(Object x) { ids.add(getValue(x)); } });
	}

	@Override
	public Object recurse(Tree t, Map<String,Functor> push_registers)
	{
		super.recurse(t, registers);

		Collection<DeclField> fields = new ArrayList<DeclField>();

		for (String id : ids)
			fields.add(new DeclField(type, id));

		return fields;
	}
}

class RNodeFormal extends RNode
{
	protected Type type;
	protected String id;

	public RNodeFormal()
	{
		registers.put("type", new Functor() { public void apply(Object x) { type = (Type)x; } });
		registers.put("ID", new Functor() { public void apply(Object x) { id = getValue(x); } });
	}

	@Override
	public Object recurse(Tree t, Map<String,Functor> push_registers)
	{
		super.recurse(t, registers);

		return new Parameter(type, id);
	}
}

class RNodeMethod extends RNode
{
	protected boolean isStatic;
	protected Type type;
	protected String name;
	protected List<Parameter> formals = new ArrayList<Parameter>();
	protected List<Statement> statements = new ArrayList<Statement>();

	public RNodeMethod()
	{
		registers.put("static", new Functor() { public void apply(Object x) { isStatic = true; } });
		registers.put("weaktype", new Functor() { public void apply(Object x) { type = (Type)x; } });
		registers.put("ID", new Functor() { public void apply(Object x) { name = getValue(x); } });
		registers.put("formal", new Functor() { public void apply(Object x) { formals.add((Parameter)x); } });
		registers.put("stmt", new Functor() { public void apply(Object x) { statements.add((Statement)x); } });
	}

	@Override
	public Object recurse(Tree t, Map<String,Functor> push_registers)
	{
		super.recurse(t, registers);

		return isStatic ? new DeclStaticMethod(type, name, formals, statements) : new DeclVirtualMethod(type, name, formals, statements);
	}
}

class RNodeType extends RNode
{
	protected Type type;

	public RNodeType()
	{
		registers.put("string", new Functor() { public void apply(Object x) { type = new PrimitiveType(getLine(x), PrimitiveType.DataType.STRING); } });
		registers.put("int", new Functor() { public void apply(Object x) { type = new PrimitiveType(getLine(x), PrimitiveType.DataType.INT); } });
		registers.put("boolean", new Functor() { public void apply(Object x) { type = new PrimitiveType(getLine(x), PrimitiveType.DataType.BOOLEAN); } });
		registers.put("void", new Functor() { public void apply(Object x) { type = new PrimitiveType(getLine(x), PrimitiveType.DataType.VOID); } });
		registers.put("CLASS_ID", new Functor() { public void apply(Object x) { type = new ClassType(getLine(x), getValue(x)); } });
		registers.put("[", new Functor() { public void apply(Object x) { type.incrementDimension(); } });
		registers.put("type", new Functor() { public void apply(Object x) { type = (Type)x; } });
	}

	@Override
	public Object recurse(Tree t, Map<String,Functor> push_registers)
	{
		super.recurse(t, registers);

		return type;
	}
}

class RNodeStmt extends RNode
{
	protected List<Statement> stmts = new ArrayList<Statement>();

	public RNodeStmt()
	{
		Functor stmts_adder = new Functor() { public void apply(Object x) { if(x != null && x instanceof Statement) stmts.add((Statement)x); } };
		
		for(String e : new String[] { "stmt", "stmt_if", "stmt_ifelse", "stmt_location", "stmt_call", "stmt_return", "stmt_while", "stmt_break", "stmt_continue", "stmt_block", "stmt_type" })
		{
			registers.put(e, stmts_adder);
		}
	}

	@Override
	public Object recurse(Tree t, Map<String,Functor> push_registers)
	{
		super.recurse(t, registers);
		
		return stmts.isEmpty() ? null : stmts.get(0);
	}
}

class RNodeStmtIf extends RNodeStmt
{
	protected Expression expr;
	
	public RNodeStmtIf()
	{
		registers.put("expr", new Functor() { public void apply(Object x) { expr = (Expression)x; } });
	}

	@Override
	public Object recurse(Tree t, Map<String,Functor> push_registers)
	{
		super.recurse(t, registers);
		
		return stmts.size() == 1 ? new StmtIf(expr, stmts.get(0)) : new StmtIf(expr, stmts.get(0), stmts.get(1));
	}
}

class RNodeStmtLocation extends RNodeStmt
{
	Ref location;
	Expression expr;
	
	public RNodeStmtLocation()
	{
		registers.put("location", new Functor() { public void apply(Object x) {
			location = (Ref)x; } });
		registers.put("expr", new Functor() { public void apply(Object x) {
			expr = (Expression)x; }
		});
	}

	@Override
	public Object recurse(Tree t, Map<String,Functor> push_registers)
	{
		super.recurse(t, registers);
		
		return new StmtAssignment(location, expr);
	}
}

class RNodeStmtCall extends RNodeStmt
{
	Call call;
	
	public RNodeStmtCall()
	{
		registers.put("static_call", new Functor() { public void apply(Object x) { call = (Call)x; } });
		registers.put("virtual_call", new Functor() { public void apply(Object x) { call = (Call)x; } });
	}

	@Override
	public Object recurse(Tree t, Map<String,Functor> push_registers)
	{
		super.recurse(t, registers);
		
		return new StmtCall(call);
	}
}

class RNodeStmtReturn extends RNodeStmt
{
	int line;
	Expression expr;
	
	public RNodeStmtReturn()
	{
		registers.put("return", new Functor() { public void apply(Object x) { line = getLine(x); } });
		registers.put("expr", new Functor() { public void apply(Object x) { expr = (Expression)x; } });
	}

	@Override
	public Object recurse(Tree t, Map<String,Functor> push_registers)
	{
		super.recurse(t, registers);
		
		return expr != null ? new StmtReturn(line, expr) : new StmtReturn(line);
	}
}

class RNodeStmtWhile extends RNodeStmt
{
	Expression expr;
	
	public RNodeStmtWhile()
	{
		registers.put("expr", new Functor() { public void apply(Object x) { expr = (Expression)x; } });
	}

	@Override
	public Object recurse(Tree t, Map<String,Functor> push_registers)
	{
		super.recurse(t, registers);
		
		return new StmtWhile(expr, stmts.get(0));
	}
}

class RNodeStmtBreak extends RNodeStmt
{
	int line;
	
	public RNodeStmtBreak()
	{
		registers.put("break", new Functor() { public void apply(Object x) { line = getLine(x); } });
	}

	@Override
	public Object recurse(Tree t, Map<String,Functor> push_registers)
	{
		super.recurse(t, registers);
		
		return new StmtBreak(line);
	}
}

class RNodeStmtContinue extends RNodeStmt
{
	int line;
	
	public RNodeStmtContinue()
	{
		registers.put("continue", new Functor() { public void apply(Object x) { line = getLine(x); } });
	}

	@Override
	public Object recurse(Tree t, Map<String,Functor> push_registers)
	{
		super.recurse(t, registers);
		
		return new StmtContinue(line);
	}
}

class RNodeStmtBlock extends RNodeStmt
{
	int line;
	
	public RNodeStmtBlock()
	{
		registers.put("{", new Functor() { public void apply(Object x) { line = getLine(x); } });
	}

	@Override
	public Object recurse(Tree t, Map<String,Functor> push_registers)
	{
		super.recurse(t, registers);
		
		return new StmtBlock(line, stmts);
	}
}

class RNodeStmtType extends RNodeStmt
{
	Parameter var;
	Expression expr;
	
	public RNodeStmtType()
	{
		registers.put("formal", new Functor() { public void apply(Object x) { var = (Parameter)x; } });
		registers.put("expr", new Functor() { public void apply(Object x) { expr = (Expression)x; } });
	}

	@Override
	public Object recurse(Tree t, Map<String,Functor> push_registers)
	{
		super.recurse(t, registers);
		
		return expr == null ? new LocalVariable(var.getLine(), var.getType(), var.getName()) : new LocalVariable(var.getLine(), var.getType(), var.getName(), expr);
	}
}

class RNodeExpr extends RNode
{
	protected List<Expression> exprs = new ArrayList<Expression>();
//	Functor previoud_expr_call;

	public RNodeExpr()
	{
		Functor exprs_adder = new Functor() { public void apply(Object x) { if(x != null && x instanceof Expression) exprs.add((Expression)x); } };
		
		for(String e : new String[] { "expr", "static_call", "virtual_call", "literal", "location", "this", "expr12", "expr11", "expr10", "expr09", "expr08", "expr07", "expr06", "expr05", "expr04", "expr03", "expr01" })
		{
			registers.put(e, exprs_adder);
		}
	}

	@Override
	public Object recurse(Tree t, Map<String,Functor> push_registers)
	{
		super.recurse(t, merge(push_registers, registers));
		
		return exprs.isEmpty() ? null : exprs.get(0);
	}
}

class RNodeExprStaticCall extends RNodeExpr
{
	protected String name;
	protected String id;
	protected int line;

	public RNodeExprStaticCall()
	{
		registers.put("CLASS_ID", new Functor() { public void apply(Object x) { name = getValue(x); } });
		registers.put("ID", new Functor() { public void apply(Object x) { id = getValue(x); line = getLine(x); } });
	}

	@Override
	public Object recurse(Tree t, Map<String,Functor> push_registers)
	{
		super.recurse(t, registers);
		
		return new StaticCall(line, name, id, exprs);
	}
}

class RNodeExprVirtualCall extends RNodeExpr
{
	protected boolean hasThis;
	protected String id;
	protected int line;

	public RNodeExprVirtualCall()
	{
		registers.put(".", new Functor() { public void apply(Object x) { hasThis = true; } });
		registers.put("ID", new Functor() { public void apply(Object x) { id = getValue(x); line = getLine(x); } });
	}

	@Override
	public Object recurse(Tree t, Map<String,Functor> push_registers)
	{
		super.recurse(t, registers);
		
		return hasThis ? new VirtualCall(line, exprs.get(0), id, exprs.subList(1, exprs.size())) : new VirtualCall(line, id, exprs);
	}
}

class RNodeExprLiteral extends RNodeExpr
{
	protected Literal literal;

	public RNodeExprLiteral()
	{
		registers.put("STRING", new Functor() { public void apply(Object x) { literal = new Literal(getLine(x), PrimitiveType.DataType.STRING, (Object)uneacapeString(getValue(x))); } });
		registers.put("INTEGER", new Functor() { public void apply(Object x) { try { literal = new Literal(getLine(x), PrimitiveType.DataType.INT, Integer.parseInt(getValue(x))); } catch (NumberFormatException e) {  throw new ParserException("numeric literal out of range: " + getValue(x), getLine(x), getColumn(x)); } } });
		registers.put("true", new Functor() { public void apply(Object x) { literal = new Literal(getLine(x), PrimitiveType.DataType.BOOLEAN, Boolean.parseBoolean(getValue(x))); } });
		registers.put("false", new Functor() { public void apply(Object x) { literal = new Literal(getLine(x), PrimitiveType.DataType.BOOLEAN, Boolean.parseBoolean(getValue(x))); } });
		registers.put("null", new Functor() { public void apply(Object x) { literal = new Literal(getLine(x), PrimitiveType.DataType.VOID, null); } });
	}

	@Override
	public Object recurse(Tree t, Map<String,Functor> push_registers)
	{
		if(push_registers.containsKey("negate"))
		{
			Mutable<Boolean> x = new Mutable<Boolean>();
			
			push_registers.get("negate").apply(x);
			
			if(x.t)
			{
				registers.put("INTEGER", new Functor() { public void apply(Object x) { try { literal = new Literal(getLine(x), PrimitiveType.DataType.INT, Integer.parseInt("-" + getValue(x))); } catch (NumberFormatException e) {  throw new ParserException("numeric literal out of range: " + "-" + getValue(x), getLine(x), getColumn(x)); } } });
			}
		}
		
		super.recurse(t, registers);

		return literal;
	}
	
	protected String uneacapeString(String s)
	{
		StreamTokenizer parser = new StreamTokenizer(new StringReader(s));
		try { parser.nextToken(); return parser.sval; } catch (IOException e1) { return s; }
	}
}

class RNodeExprLocation extends RNodeExpr
{
	protected boolean hasThis;
	protected String id;
	protected int line;

	public RNodeExprLocation()
	{
		registers.put("ID", new Functor() { public void apply(Object x) { id = getValue(x); line = getLine(x); } });
	}

	@Override
	public Object recurse(Tree t, Map<String,Functor> push_registers)
	{
		super.recurse(t, registers);

		switch(exprs.size())
		{
		case 0:
			return new RefVariable(line, id);
			
		case 1:
			return new RefField(line, exprs.get(0), id);
			
		default:
			return new RefArrayElement(exprs.get(0), exprs.get(1));
		}
	}
}

class RNodeExprThis extends RNodeExpr
{
	@Override
	public Object recurse(Tree t, Map<String,Functor> push_registers)
	{
		return new This(getLine(t));
	}
}

class RNodeExprUnaryOp extends RNodeExpr
{
	int line;
	String op;
	
	public RNodeExprUnaryOp()
	{
		registers.put("-", new Functor() { public void apply(Object x) { line = getLine(x); op = getValue(x); } });
		registers.put("!", new Functor() { public void apply(Object x) { line = getLine(x); op = getValue(x); } });
	}

	@Override
	public Object recurse(Tree t, Map<String,Functor> push_registers)
	{
		if(!push_registers.containsKey("negate"))
		{
			registers.put("negate", new Functor() { @SuppressWarnings("unchecked") public void apply(Object x) { ((Mutable<Boolean>)x).t = op != null && op.compareTo("-") == 0; } });
		}
		
		Object x = super.recurse(t, merge(push_registers, registers));
		
		return op == null || (op.compareTo("-") == 0 && x instanceof Literal && ((Literal)x).getType() == DataType.INT) ? x : new UnaryOp(line, UnaryOp.UnaryOps.find(op), exprs.get(0));
	}
}

class RNodeExprBinaryOp extends RNodeExpr
{
	int line;
	String op;
	
	public RNodeExprBinaryOp()
	{
		Functor op_selector = new Functor() { public void apply(Object x) { line = getLine(x); op = getValue(x); } };
		
		for(String e : new String[] { "||", "&&", "==", "!=", "<", ">", "<=", ">=", "+", "-", "*", "/", "%" })
		{
			registers.put(e, op_selector);
		}
	}

	@Override
	public Object recurse(Tree t, Map<String,Functor> push_registers)
	{
		Object x = super.recurse(t, merge(push_registers, registers));
		
		return op != null ? new BinaryOp(line, exprs.get(0), BinaryOp.BinaryOps.find(op), exprs.get(1)) : x;
	}
}

class RNodeExprLength extends RNodeExpr
{
	int line;
	boolean hasLength;
	
	public RNodeExprLength()
	{
		registers.put("length", new Functor() { public void apply(Object x) { line = getLine(x); hasLength = true; } });
	}

	@Override
	public Object recurse(Tree t, Map<String,Functor> push_registers)
	{
		Object x = super.recurse(t, merge(push_registers, registers));
		
		return hasLength ? new Length(line, exprs.get(0)) : x;
	}
}

class RNodeExprNewInstance extends RNodeExpr
{
	int line;
	boolean hasNew;
	String id;
	
	public RNodeExprNewInstance()
	{
		registers.put("new", new Functor() { public void apply(Object x) { line = getLine(x); hasNew = true; } });
		registers.put("CLASS_ID", new Functor() { public void apply(Object x) { id = getValue(x); } });
	}

	@Override
	public Object recurse(Tree t, Map<String,Functor> push_registers)
	{
		Object x = super.recurse(t, merge(push_registers, registers));
		
		return hasNew ? new NewInstance(line, id) : x;
	}
}

class RNodeExprNewArray extends RNodeExpr
{
	boolean hasNew;
	Type type;
	
	public RNodeExprNewArray()
	{
		registers.put("new", new Functor() { public void apply(Object x) { hasNew = true; } });
		registers.put("type", new Functor() { public void apply(Object x) { type = (Type)x; } });
	}

	@Override
	public Object recurse(Tree t, Map<String,Functor> push_registers)
	{
		Object x = super.recurse(t, merge(push_registers, registers));
		
		return hasNew ? new NewArray(type, exprs.get(0)) : x;
	}
}
